{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "dfccd8e6",
   "metadata": {},
   "source": [
    "# Fine-Tuning GPT-2 on Encrypted Data with LoRA and Concrete ML\n",
    "\n",
    "In this notebook, we perform fine-tuning of a GPT-2 model using LoRA and Concrete ML."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "eca73e44",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<torch._C.Generator at 0x7f3c5c7145d0>"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Import necessary libraries\n",
    "import math\n",
    "import shutil\n",
    "from pathlib import Path\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import torch\n",
    "from datasets import Dataset\n",
    "from peft import LoraConfig, get_peft_model\n",
    "from tqdm import tqdm\n",
    "from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments\n",
    "from utils_lora import generate_and_print, print_weights_and_size\n",
    "\n",
    "from concrete.ml.torch.hybrid_model import HybridFHEModel\n",
    "from concrete.ml.torch.lora import LoraTraining, get_remote_names\n",
    "\n",
    "# Set random seed for reproducibility\n",
    "SEED = 0\n",
    "torch.manual_seed(SEED)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "8b965a1a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "94f22e4cc0df4edb8b3cf515f08392c3",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "tokenizer_config.json:   0%|          | 0.00/26.0 [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "814f62f9fb7248cab0740c0f386787a5",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "config.json:   0%|          | 0.00/665 [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "6cc8a8a3b8044d4f877113a5b5a76056",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "vocab.json:   0%|          | 0.00/1.04M [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "5d1335e0c14749cebe9638a4c0dc1805",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "merges.txt:   0%|          | 0.00/456k [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "ae5b36d386494a4382d50bba6a92d0a5",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "tokenizer.json:   0%|          | 0.00/1.36M [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "33592167f9df46198151efee6c2c8217",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "model.safetensors:   0%|          | 0.00/548M [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "94d89a8d82b44a1786ec4f64835472f5",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "generation_config.json:   0%|          | 0.00/124 [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Load pre-trained GPT-2 model and tokenizer\n",
    "model_name = \"gpt2\"\n",
    "tokenizer = AutoTokenizer.from_pretrained(model_name)\n",
    "model = AutoModelForCausalLM.from_pretrained(model_name)\n",
    "\n",
    "# Ensure tokenizer has a pad token\n",
    "if tokenizer.pad_token is None:\n",
    "    tokenizer.pad_token = tokenizer.eos_token\n",
    "model.config.pad_token_id = model.config.eos_token_id\n",
    "\n",
    "# Freeze model weights\n",
    "for param in model.parameters():\n",
    "    param.requires_grad = False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "2337a6b4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a new type of energy storage that is designed to be used in a variety of applications. It is used to store energy in\n"
     ]
    }
   ],
   "source": [
    "generate_and_print(\n",
    "    prompt=\"What is FHE?\",\n",
    "    model=model,\n",
    "    tokenizer=tokenizer,\n",
    "    seed=SEED,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "20564b59",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Apply LoRA to the model\n",
    "# target_modules can be set to \"all-linear\"\n",
    "# to target all modules. By default only the\n",
    "# c_attn projection are fine-tuned with lora.\n",
    "peft_config = LoraConfig(\n",
    "    r=8,\n",
    "    lora_alpha=32,\n",
    "    lora_dropout=0.1,\n",
    "    bias=\"none\",\n",
    "    task_type=\"CAUSAL_LM\",\n",
    ")\n",
    "peft_model = get_peft_model(model, peft_config)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "5ac49f9d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "LoRA layers detected in the model.\n"
     ]
    }
   ],
   "source": [
    "# Set up LoRA training\n",
    "lora_training = LoraTraining(peft_model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "d10d71e8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "a4f65111d05b41cfbcda37585bfe8cda",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Map:   0%|          | 0/34 [00:00<?, ? examples/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Prepare dataset for fine-tuning\n",
    "BLOCK_SIZE = 128\n",
    "\n",
    "# Read lines from the file\n",
    "with open(\"data_finetune/what_is_fhe.txt\", \"r\", encoding=\"utf-8\") as f:\n",
    "    lines = f.readlines()\n",
    "\n",
    "# Remove empty lines and strip whitespace\n",
    "lines = [line.strip() for line in lines if line.strip()]\n",
    "\n",
    "# Group lines into question-answer pairs\n",
    "examples = []\n",
    "for i in range(0, len(lines) - 1, 2):\n",
    "    question = lines[i]\n",
    "    answer = lines[i + 1]\n",
    "    examples.append({\"question\": question, \"answer\": answer})\n",
    "\n",
    "# Create a Dataset object from the list of examples\n",
    "dataset = Dataset.from_list(examples)\n",
    "\n",
    "\n",
    "# Tokenization function\n",
    "def tokenize_function(examples):\n",
    "    input_ids_list = []\n",
    "    labels_list = []\n",
    "    attention_masks_list = []\n",
    "    for question, answer in zip(examples[\"question\"], examples[\"answer\"]):\n",
    "        # Tokenize question and answer separately\n",
    "        question_encoding = tokenizer(\n",
    "            question, add_special_tokens=False, truncation=True, max_length=BLOCK_SIZE // 2\n",
    "        )\n",
    "        answer_encoding = tokenizer(\n",
    "            answer, add_special_tokens=False, truncation=True, max_length=BLOCK_SIZE // 2 - 1\n",
    "        )\n",
    "\n",
    "        # Build input_ids\n",
    "        input_ids = (\n",
    "            question_encoding[\"input_ids\"]\n",
    "            + [tokenizer.eos_token_id]\n",
    "            + answer_encoding[\"input_ids\"]\n",
    "            + [tokenizer.eos_token_id]\n",
    "        )\n",
    "\n",
    "        # Build labels: -100 for question tokens and eos token after question\n",
    "        labels = (\n",
    "            [-100] * len(question_encoding[\"input_ids\"])\n",
    "            + [-100]  # For the eos token after question\n",
    "            + answer_encoding[\"input_ids\"]\n",
    "            + [tokenizer.eos_token_id]\n",
    "        )\n",
    "\n",
    "        # Create attention mask: 1 for real tokens, 0 for padding\n",
    "        attention_mask = [1] * len(input_ids)\n",
    "\n",
    "        # Pad/truncate to BLOCK_SIZE\n",
    "        padding_length = BLOCK_SIZE - len(input_ids)\n",
    "        if padding_length > 0:\n",
    "            input_ids += [tokenizer.pad_token_id] * padding_length\n",
    "            labels += [-100] * padding_length\n",
    "            attention_mask += [0] * padding_length\n",
    "        else:\n",
    "            input_ids = input_ids[:BLOCK_SIZE]\n",
    "            labels = labels[:BLOCK_SIZE]\n",
    "            attention_mask = attention_mask[:BLOCK_SIZE]\n",
    "\n",
    "        input_ids_list.append(input_ids)\n",
    "        labels_list.append(labels)\n",
    "        attention_masks_list.append(attention_mask)\n",
    "\n",
    "    return {\n",
    "        \"input_ids\": input_ids_list,\n",
    "        \"labels\": labels_list,\n",
    "        \"attention_mask\": attention_masks_list,\n",
    "    }\n",
    "\n",
    "\n",
    "# Apply the tokenization\n",
    "tokenized_datasets = dataset.map(\n",
    "    tokenize_function, batched=True, remove_columns=[\"question\", \"answer\"]\n",
    ")\n",
    "\n",
    "# Since we've already handled padding and labels, we can use a custom data collator\n",
    "\n",
    "\n",
    "def data_collator(features):\n",
    "    batch = {}\n",
    "    batch[\"input_ids\"] = torch.tensor([f[\"input_ids\"] for f in features], dtype=torch.long)\n",
    "    batch[\"labels\"] = torch.tensor([f[\"labels\"] for f in features], dtype=torch.long)\n",
    "    batch[\"attention_mask\"] = torch.tensor(\n",
    "        [f[\"attention_mask\"] for f in features], dtype=torch.long\n",
    "    )\n",
    "    return batch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "8a01acd1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define training arguments\n",
    "EPOCHS = 20\n",
    "PER_DEVICE_TRAIN_BATCH_SIZE = 4\n",
    "\n",
    "training_args = TrainingArguments(\n",
    "    output_dir=\"./checkpoints\",\n",
    "    num_train_epochs=EPOCHS,\n",
    "    per_device_train_batch_size=PER_DEVICE_TRAIN_BATCH_SIZE,\n",
    "    gradient_accumulation_steps=1,\n",
    "    save_total_limit=1,\n",
    "    use_cpu=True,\n",
    "    learning_rate=2e-3,\n",
    "    lr_scheduler_type=\"linear\",\n",
    "    seed=SEED,\n",
    "    data_seed=SEED,\n",
    "    warmup_steps=10,\n",
    "    weight_decay=0.01,\n",
    "    prediction_loss_only=True,\n",
    ")\n",
    "\n",
    "\n",
    "def causal_lm_loss(logits, labels, ignore_index=-100):\n",
    "    # Shift logits and labels for next-token prediction\n",
    "    shift_logits = logits[..., :-1, :].contiguous()\n",
    "    shift_labels = labels[..., 1:].contiguous()\n",
    "\n",
    "    # Flatten the tensors\n",
    "    shift_logits = shift_logits.view(-1, shift_logits.size(-1))\n",
    "    shift_labels = shift_labels.view(-1)\n",
    "\n",
    "    # Compute the loss, ignoring padding tokens\n",
    "    loss = torch.nn.functional.cross_entropy(\n",
    "        shift_logits, shift_labels, ignore_index=ignore_index, reduction=\"mean\"\n",
    "    )\n",
    "\n",
    "    return loss"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "3c8864b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Initialize Trainer\n",
    "trainer = Trainer(\n",
    "    model=peft_model,\n",
    "    args=training_args,\n",
    "    train_dataset=tokenized_datasets,\n",
    "    data_collator=data_collator,\n",
    ")\n",
    "\n",
    "# Prepare for training\n",
    "train_dataloader = trainer.get_train_dataloader()\n",
    "\n",
    "len_dataloader = len(train_dataloader)\n",
    "num_update_steps_per_epoch = len_dataloader // training_args.gradient_accumulation_steps\n",
    "num_update_steps_per_epoch = max(num_update_steps_per_epoch, 1)\n",
    "max_steps = math.ceil(training_args.num_train_epochs * num_update_steps_per_epoch)\n",
    "\n",
    "trainer.create_optimizer_and_scheduler(num_training_steps=max_steps)\n",
    "\n",
    "lr_scheduler = trainer.lr_scheduler\n",
    "optimizer = trainer.optimizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "ae2094a4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Get the names of the remote modules (layers to be converted to FHE)\n",
    "remote_names = get_remote_names(lora_training, include_embedding_layers=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "a21298ee",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create the HybridFHEModel with the specified remote modules\n",
    "hybrid_model = HybridFHEModel(lora_training, module_names=remote_names)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "56ec41b8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Prepare input data for calibration\n",
    "input_tensor = torch.randint(\n",
    "    0, tokenizer.vocab_size, (PER_DEVICE_TRAIN_BATCH_SIZE, BLOCK_SIZE), dtype=torch.long\n",
    ")\n",
    "label_tensor = torch.randint(\n",
    "    0, tokenizer.vocab_size, (PER_DEVICE_TRAIN_BATCH_SIZE, BLOCK_SIZE), dtype=torch.long\n",
    ")\n",
    "attention_mask = torch.ones((PER_DEVICE_TRAIN_BATCH_SIZE, BLOCK_SIZE), dtype=torch.long)\n",
    "\n",
    "inputset = {\"input_ids\": input_tensor, \"attention_mask\": attention_mask, \"labels\": label_tensor}\n",
    "\n",
    "# inputset = (input_tensor, label_tensor, attention_mask)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "20dfe2d8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "876b3dbc66cd440b9303da229fbcbba1",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Compiling FHE layers:   0%|          | 0/95 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Calibrate and compile the model\n",
    "lora_training.toggle_calibrate(enable=True)\n",
    "hybrid_model.compile_model(inputset, n_bits=16)\n",
    "lora_training.toggle_calibrate(enable=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "18e450e6",
   "metadata": {},
   "outputs": [],
   "source": [
    "def train_custom_model(\n",
    "    hybrid_model, train_dataloader, training_args, tokenizer, fhe=\"disable\"\n",
    "):  # pylint: disable=too-many-locals\n",
    "    device = \"cpu\"\n",
    "    hybrid_model.model.to(device)\n",
    "\n",
    "    # Training loop\n",
    "    peft_model.train()\n",
    "    total_epochs = int(training_args.num_train_epochs)\n",
    "    epoch_pbar = tqdm(total=total_epochs, desc=\"Training Progress\", position=0)\n",
    "\n",
    "    total_batched_samples = 0\n",
    "    epoch_losses = []\n",
    "\n",
    "    # Generate text before the first epoch\n",
    "    print(\"Generating text before the first epoch:\\n\")\n",
    "    prompt = \"What is FHE?\"\n",
    "    hybrid_model.set_fhe_mode(\"disable\")\n",
    "    generate_and_print(prompt, peft_model, tokenizer, SEED)\n",
    "    hybrid_model.set_fhe_mode(fhe)\n",
    "\n",
    "    for epoch in range(total_epochs):\n",
    "        total_loss = 0\n",
    "        grad_norms = []\n",
    "\n",
    "        for _, batch in enumerate(train_dataloader):\n",
    "            total_batched_samples += 1\n",
    "            batch = {k: v.to(device) for k, v in batch.items()}\n",
    "\n",
    "            # Zero the gradients\n",
    "            optimizer.zero_grad()\n",
    "\n",
    "            # Forward pass\n",
    "            loss, grad_norm = hybrid_model(batch, fhe=fhe)\n",
    "\n",
    "            # Optimizer step\n",
    "            optimizer.step()\n",
    "\n",
    "            # Learning rate scheduler step\n",
    "            lr_scheduler.step()\n",
    "\n",
    "            total_loss += loss.item()\n",
    "            if grad_norm is not None:\n",
    "                grad_norms.append(grad_norm)\n",
    "\n",
    "        # Get current learning rate\n",
    "        current_lr = lr_scheduler.get_last_lr()[0]\n",
    "\n",
    "        # Get last grad norm\n",
    "        current_grad_norm = grad_norms[-1] if grad_norms else None\n",
    "\n",
    "        # Store the total loss for this epoch\n",
    "        epoch_losses.append(total_loss)\n",
    "\n",
    "        # Log epoch results\n",
    "        print(\n",
    "            f\"Epoch {epoch + 1}/{training_args.num_train_epochs}, \"\n",
    "            f\"Loss: {total_loss:.4f}, grad norm: {current_grad_norm}, lr: {current_lr}\"\n",
    "        )\n",
    "\n",
    "        # Generate text after each epoch\n",
    "        prompt = \"What is FHE?\"\n",
    "        hybrid_model.set_fhe_mode(\"disable\")\n",
    "        generate_and_print(prompt, peft_model, tokenizer, SEED)\n",
    "        hybrid_model.set_fhe_mode(fhe)\n",
    "\n",
    "        print(\"\\n\" + \"-\" * 50)  # Separator for readability\n",
    "        epoch_pbar.update(1)\n",
    "\n",
    "    # Save model checkpoint\n",
    "    if training_args.output_dir is not None:\n",
    "        save_path = f\"{training_args.output_dir}/checkpoint-{epoch + 1}\"\n",
    "        peft_model.save_pretrained(save_path)\n",
    "\n",
    "    epoch_pbar.close()\n",
    "\n",
    "    # Plot the loss evolution\n",
    "    plt.figure(figsize=(10, 6))\n",
    "    plt.plot(range(1, total_epochs + 1), epoch_losses, marker=\"o\")\n",
    "    plt.title(\"Loss Evolution During Training\")\n",
    "    plt.xlabel(\"Epoch\")\n",
    "    plt.ylabel(\"Total Loss\")\n",
    "    plt.grid(True)\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "0ca82a81",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:   0%|          | 0/20 [00:00<?, ?it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Generating text before the first epoch:\n",
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a new type of energy that is used to generate electricity. It is the most energy-efficient form of electricity, and it\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/20, Loss: 31.7642, grad norm: None, lr: 0.0018000000000000002\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:   5%|▌         | 1/20 [00:21<06:52, 21.71s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a non-invasive, noninvasible, and nonflammable, biocompatible, anti-inflammatory\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 2/20, Loss: 26.4969, grad norm: None, lr: 0.0019058823529411763\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  10%|█         | 2/20 [00:27<03:42, 12.38s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a simple, fast, efficient, and highly secure encryption scheme. It can be used to encrypt data, or to decrypt it\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 3/20, Loss: 23.2779, grad norm: None, lr: 0.0018000000000000002\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  15%|█▌        | 3/20 [00:31<02:28,  8.71s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a simple, fast, and efficient way to perform computations on Fully-Fully Homomorphic Encryption (F-\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 4/20, Loss: 20.6254, grad norm: None, lr: 0.0016941176470588236\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  20%|██        | 4/20 [00:37<01:57,  7.32s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a non-static structure that can only be used by nonstructural operators such as functions or functions. It is used to\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 5/20, Loss: 19.4243, grad norm: None, lr: 0.001588235294117647\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  25%|██▌       | 5/20 [00:41<01:34,  6.31s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a type of Fully Homomorphic Encryption (Fully Homogeneous Encrypted) that can be used to perform computationally computable\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 6/20, Loss: 17.2734, grad norm: None, lr: 0.0014823529411764707\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  30%|███       | 6/20 [00:46<01:19,  5.66s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a type of Fully Homomorphic Encryption (FHO) that can be used to perform computations on encrypted data. F\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 7/20, Loss: 15.0494, grad norm: None, lr: 0.001376470588235294\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  35%|███▌      | 7/20 [00:50<01:09,  5.34s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a type of Fully Homomorphic Encryption (FHO) that allows computations on encrypted data without revealing the underlying underlying data\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 8/20, Loss: 14.6020, grad norm: None, lr: 0.0012705882352941175\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  40%|████      | 8/20 [00:55<01:00,  5.04s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a fast-growing encryption algorithm that allows for secure, encrypted data anywhere on the network. It supports both encrypted and plaintext\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 9/20, Loss: 13.6395, grad norm: None, lr: 0.0011647058823529412\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  45%|████▌     | 9/20 [01:00<00:55,  5.04s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a fast, low-latency, encrypted, superfast, high-performance computing model for machine learning. It enables machine-\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 10/20, Loss: 12.9375, grad norm: None, lr: 0.0010588235294117648\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  50%|█████     | 10/20 [01:04<00:49,  4.93s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a fast, low-latency, encrypted neural network (FLEN) that can be used to perform computations on encrypted\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 11/20, Loss: 11.3290, grad norm: None, lr: 0.0009529411764705882\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  55%|█████▌    | 11/20 [01:09<00:42,  4.76s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHO is a type of Fully Homomorphic Encryption (FHE) that uses a mathematical operation to encrypt data. Fhat is an efficient way\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 12/20, Loss: 10.4882, grad norm: None, lr: 0.0008470588235294118\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  60%|██████    | 12/20 [01:13<00:37,  4.66s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? A Fully Homomorphic Encryption (FHE) is a technology that allows data to be decrypted without ever decryption. This allows for secure comput\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 13/20, Loss: 9.8948, grad norm: None, lr: 0.0007411764705882353\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  65%|██████▌   | 13/20 [01:18<00:32,  4.69s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? A Fully Homomorphic Encryption (FHE) is a technology that allows secure computation and data computation without exposing the encrypted information to external parties. F\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 14/20, Loss: 9.1278, grad norm: None, lr: 0.0006352941176470588\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  70%|███████   | 14/20 [01:23<00:29,  4.90s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? Fully Homomorphic Encryption (FHE) allows data to be processed without ever decrypting it, rendering it useless. This makes it ideal for applications\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 15/20, Loss: 8.8226, grad norm: None, lr: 0.0005294117647058824\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  75%|███████▌  | 15/20 [01:28<00:23,  4.79s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a fast encryption technique that allows encrypted data to be decrypted without ever decrypting the data. This allows for encrypted computations to\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 16/20, Loss: 8.3894, grad norm: None, lr: 0.0004235294117647059\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  80%|████████  | 16/20 [01:32<00:18,  4.69s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a fast-growing fast growing economy that can be used to encrypt and decryption of data. It is estimated that the average F\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 17/20, Loss: 7.5363, grad norm: None, lr: 0.0003176470588235294\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  85%|████████▌ | 17/20 [01:37<00:14,  4.71s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a fast encryption process that allows encrypted data to be processed without needing to decrypt it. This makes it possible for users to securely record\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 18/20, Loss: 7.3201, grad norm: None, lr: 0.00021176470588235295\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  90%|█████████ | 18/20 [01:42<00:09,  4.86s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a fast encryption method that allows arbitrary operations on encrypted data without needing to decrypt it. It allows for computations on sensitive information,\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 19/20, Loss: 7.3124, grad norm: None, lr: 0.00010588235294117647\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress:  95%|█████████▌| 19/20 [01:47<00:04,  4.78s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a fast learner-first-fast-forward-fHE scheme that supports batching and can be used for secure computations\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 20/20, Loss: 6.9397, grad norm: None, lr: 0.0\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress: 100%|██████████| 20/20 [01:51<00:00,  4.68s/it]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? FHE is a fast learner-first-fast-forward-fHE scheme that supports batching and can be used for secure computations\n",
      "\n",
      "--------------------------------------------------\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r",
      "Training Progress: 100%|██████████| 20/20 [01:51<00:00,  5.60s/it]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0kAAAIjCAYAAADWYVDIAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuNSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/xnp5ZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB17ElEQVR4nO3dd3wVZaLG8WdOKiGNBNIgQOiEANKJCtKLLoig2FBQV1fEiuy1rApY1rK21VVsKCKigAVlVRAQEBQIRXqHUNMgIYWEhJAz9w9MNiGFBJIzJ8nv+/nkLpnznslzXuYe8zAz7zFM0zQFAAAAAJAk2awOAAAAAADOhJIEAAAAAIVQkgAAAACgEEoSAAAAABRCSQIAAACAQihJAAAAAFAIJQkAAAAACqEkAQAAAEAhlCQAAAAAKISSBAAo0cGDB2UYhmbMmFGp+50yZYoMw6jUfTq7Pn36qE+fPlbHqBKXepwYhqEpU6ZUaiYAuFSUJAC11owZM2QYhtavX291lDLll4rSvhISEqyOWExWVpamTJmi5cuXWx2liMLz5urqqoCAAHXp0kUPPfSQduzYYXW8SnWh4yb/q6aWNwC4FK5WBwAAlM+0adPk7e1dbLu/v7/jw1xAVlaWpk6dKknFfgl/6qmn9Pjjj1uQ6pyBAwfq9ttvl2maSktL0+bNm/Xpp5/q3Xff1csvv6yJEydW+s/8+eefK32fFzJy5Ei1aNGi4PtTp05p/Pjxuu666zRy5MiC7cHBwZf0c5o0aaLTp0/Lzc3top5/+vRpubry6wgA58K7EgBUE9dff73q169vdYxL5urqaukvxa1atdKYMWOKbHvppZc0bNgwPfroo2rTpo2uvvrqSvlZWVlZ8vLykru7e6XsryI6dOigDh06FHx/4sQJjR8/Xh06dCj2+gvLzs6Wu7u7bLbyXWxiGIY8PT0vOuelPBcAqgqX2wHABfzxxx8aOnSofH195e3trf79+2vNmjVFxuTm5mrq1Klq2bKlPD09FRgYqCuvvFKLFy8uGJOQkKA77rhDjRo1koeHh0JDQ3Xttdfq4MGDl5wxMTFRrq6uBWdvCtu9e7cMw9B//vOfgm0HDhzQDTfcoICAAHl5ealnz5764YcfLvhzSru3Zty4cWratKmkc/eoNGjQQJI0derUgsu68u87KemepLNnz+q5555T8+bN5eHhoaZNm+rJJ59UTk5OkXFNmzbVX/7yF61atUrdu3eXp6enmjVrppkzZ14we1kCAwP15ZdfytXVVS+88ELB9vxLMs//O1q+fLkMwyhyOWGfPn0UFRWlDRs2qHfv3vLy8tKTTz5Z8Fjhect//ty5c/XCCy+oUaNG8vT0VP/+/bVv375i+d555x01a9ZMderUUffu3bVy5cpKuc8pP8eXX36pp556Sg0bNpSXl5fS09OVkpKiSZMmqX379vL29pavr6+GDh2qzZs3F9lHSfckjRs3Tt7e3jp27JhGjBghb29vNWjQQJMmTVJeXl6R559/T1L+8bFv3z6NGzdO/v7+8vPz0x133KGsrKwizz19+rQefPBB1a9fXz4+Pho+fLiOHTvGfU4ALhlnkgCgDNu3b1evXr3k6+ur//u//5Obm5vef/999enTRytWrFCPHj0knfvF7sUXX9Rf//pXde/eXenp6Vq/fr02btyogQMHSpJGjRql7du364EHHlDTpk2VlJSkxYsX6/DhwwUFoywpKSnFtrm6usrf31/BwcG66qqrNHfuXE2ePLnImDlz5sjFxUU33HCDpHOF6vLLL1dWVpYefPBBBQYG6tNPP9Xw4cP11Vdf6brrrrukOWvQoIGmTZtW7NKuwmc1zvfXv/5Vn376qa6//no9+uijWrt2rV588UXt3LlT3377bZGx+/bt0/XXX6+77rpLY8eO1ccff6xx48apS5cuateu3UXnbty4sa666iotW7ZM6enp8vX1rfA+kpOTNXToUN10000aM2bMBS9le+mll2Sz2TRp0iSlpaXplVde0a233qq1a9cWjJk2bZruv/9+9erVS4888ogOHjyoESNGqF69emrUqFGFM5bkueeek7u7uyZNmqScnBy5u7trx44dmj9/vm644QZFREQoMTFR77//vq666irt2LFDYWFhZe4zLy9PgwcPVo8ePfTqq69qyZIleu2119S8eXONHz/+gplGjx6tiIgIvfjii9q4caM++ugjBQUF6eWXXy4YM27cOM2dO1e33XabevbsqRUrVuiaa6655PkAAJkAUEt98sknpiRz3bp1pY4ZMWKE6e7ubu7fv79gW1xcnOnj42P27t27YFvHjh3Na665ptT9nDx50pRk/utf/6pwzsmTJ5uSSvxq3bp1wbj333/flGRu3bq1yPMjIyPNfv36FXz/8MMPm5LMlStXFmzLyMgwIyIizKZNm5p5eXmmaZpmbGysKcn85JNPCsZdddVV5lVXXVUs49ixY80mTZoUfH/8+HFTkjl58uRSX0++TZs2mZLMv/71r0XGTZo0yZRk/vLLLwXbmjRpYkoyf/3114JtSUlJpoeHh/noo48W+1nnk2ROmDCh1McfeughU5K5efNm0zT/d4zExsYWGbds2TJTkrls2bKCbVdddZUpyXzvvfeK7ff8ect/ftu2bc2cnJyC7f/+97+L/B3m5OSYgYGBZrdu3czc3NyCcTNmzDAllfh3UZqS/k7yczRr1szMysoqMj47O7vgWMgXGxtrenh4mM8++2yRbecfJ2PHjjUlFRlnmqbZqVMns0uXLkW2nZ8p//i48847i4y77rrrzMDAwILvN2zYYEoyH3744SLjxo0bV+qxBwDlxeV2AFCKvLw8/fzzzxoxYoSaNWtWsD00NFS33HKLVq1apfT0dEnnFk/Yvn279u7dW+K+6tSpI3d3dy1fvlwnT568qDxff/21Fi9eXOTrk08+KXh85MiRcnV11Zw5cwq2bdu2TTt27NCNN95YsO3HH39U9+7ddeWVVxZs8/b21j333KODBw86fJW3H3/8UZKKLZjw6KOPSlKxywAjIyPVq1evgu8bNGig1q1b68CBA5ecJX9hjIyMjIt6voeHh+64445yj7/jjjuK3K+U/7ryX8v69euVnJysu+++u8h9XLfeeqvq1at3URlLMnbsWNWpU6fINg8Pj4L7kvLy8pScnCxvb2+1bt1aGzduLNd+77333iLf9+rVq9x/TyU9Nzk5ueD/5xYuXChJuu+++4qMe+CBB8q1fwAoCyUJAEpx/PhxZWVlqXXr1sUea9u2rex2u44cOSJJevbZZ5WamqpWrVqpffv2+vvf/64tW7YUjPfw8NDLL7+sn376ScHBwerdu7deeeWVCi3f3bt3bw0YMKDIV3R0dMHj9evXV//+/TV37tyCbXPmzJGrq2uR1cwOHTpU6mvKf9yRDh06JJvNVmQlNkkKCQmRv79/sTyNGzcuto969epddPks7NSpU5IkHx+fi3p+w4YNK7RIw/mvJb/45L+W/Nd+/ty4urqW6xLN8oqIiCi2zW6364033lDLli3l4eGh+vXrq0GDBtqyZYvS0tIuuE9PT8+Ce9PyVeTvqTxzY7PZimU/f64A4GJQkgCgEvTu3Vv79+/Xxx9/rKioKH300Ufq3LmzPvroo4IxDz/8sPbs2aMXX3xRnp6eevrpp9W2bVv98ccflZbjpptu0p49e7Rp0yZJ0ty5c9W/f/9KWxWvtA+BPf9m/Mrc9/lcXFxK3G6a5iVn2LZtm1xcXAp+8a7o6z3/bMyFVOVrqYiScv/zn//UxIkT1bt3b82aNUuLFi3S4sWL1a5dO9nt9gvus7TXVl7OMjcAaidKEgCUokGDBvLy8tLu3buLPbZr1y7ZbDaFh4cXbAsICNAdd9yhL774QkeOHFGHDh2KrbDVvHlzPfroo/r555+1bds2nTlzRq+99lqlZR4xYoTc3d01Z84cbdq0SXv27NFNN91UZEyTJk1KfU35j5emXr16Sk1NLbb9/LM95S08+T/PbrcXu1QxMTFRqampZeapTIcPH9aKFSsUHR1dcCYp/+zF+a/ZUWfb8l/7+SvenT17tlJWRSzLV199pb59+2r69Om66aabNGjQIA0YMKDEv38r5B83sbGxRbaXtDogAFQUJQkASuHi4qJBgwbpu+++K/ILaWJiombPnq0rr7yyYAW05OTkIs/19vZWixYtCpawzsrKUnZ2dpExzZs3l4+PT7Flri+Fv7+/Bg8erLlz5+rLL7+Uu7u7RowYUWTM1VdfrZiYGK1evbpgW2Zmpj744AM1bdpUkZGRpe6/efPm2rVrl44fP16wbfPmzfrtt9+KjPPy8pJUvFyUJP8zid58880i219//XVJcshqZSkpKbr55puVl5enf/zjHwXbmzdvLkn69ddfC7bl5eXpgw8+qPJMktS1a1cFBgbqww8/1NmzZwu2f/7555VyeWFZXFxcip21mTdvno4dO1alP7e8Bg8eLEl69913i2x/++23rYgDoIZhCXAAtd7HH39ccBN4YQ899JCef/55LV68WFdeeaXuu+8+ubq66v3331dOTo5eeeWVgrGRkZHq06ePunTpooCAAK1fv15fffWV7r//fknSnj171L9/f40ePVqRkZFydXXVt99+q8TExGJnekrz1VdfFSwsUNjAgQOLLDV94403asyYMXr33Xc1ePBg+fv7Fxn/+OOP64svvtDQoUP14IMPKiAgQJ9++qliY2P19ddfl/khonfeeadef/11DR48WHfddZeSkpL03nvvqV27dgU31EvnLt+KjIzUnDlz1KpV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729+99135pYtW8xrr73WjIiIME+fPl2wj379+plvv/12wfdffvml6eHhYc6YMcPcsWOHec8995j+/v5mQkKCw1+fMyprzs+cOWMOHz7cbNSokblp06Yi7+05OTkF+zh/zi/03lSblTXfGRkZ5qRJk8zVq1ebsbGx5pIlS8zOnTubLVu2NLOzswv2wTFeMRd6XzFN00xLSzO9vLzMadOmlbgPjvHyK8/vg/fee6/ZuHFj85dffjHXr19vRkdHm9HR0UX207p1a/Obb74p+L487/+ORklCuUgq8euTTz4pGHPVVVeZY8eOLfj+4YcfNhs3bmy6u7ubwcHB5tVXX21u3LjR8eGrqRtvvNEMDQ013d3dzYYNG5o33nijuW/fvoLHz59v0zTNuXPnmq1atTLd3d3Ndu3amT/88IODU1dvixYtMiWZu3fvLvYYx/elW7ZsWYnvI/nzarfbzaefftoMDg42PTw8zP79+xf7u2jSpIk5efLkItvefvvtgr+L7t27m2vWrHHQK3J+Zc15bGxsqe/ty5YtK9jH+XN+ofem2qys+c7KyjIHDRpkNmjQwHRzczObNGli3n333cXKDsd4xVzofcU0TfP9998369SpY6amppa4D47x8ivP74OnT58277vvPrNevXqml5eXed1115nx8fHF9lP4OeV5/3c0wzRNs2rOUQEAAABA9cM9SQAAAABQCCUJAAAAAAqhJAEAAABAIZQkAAAAACiEkgQAAAAAhVCSAAAAAKAQShIAAAAAFEJJAgAAAIBCKEkAAJTBMAzNnz/f6hgAAAeiJAEAnNa4ceNkGEaxryFDhlgdDQBQg7laHQAAgLIMGTJEn3zySZFtHh4eFqUBANQGnEkCADg1Dw8PhYSEFPmqV6+epHOXwk2bNk1Dhw5VnTp11KxZM3311VdFnr9161b169dPderUUWBgoO655x6dOnWqyJiPP/5Y7dq1k4eHh0JDQ3X//fcXefzEiRO67rrr5OXlpZYtW+r777+v2hcNALAUJQkAUK09/fTTGjVqlDZv3qxbb71VN910k3bu3ClJyszM1ODBg1WvXj2tW7dO8+bN05IlS4qUoGnTpmnChAm65557tHXrVn3//fdq0aJFkZ8xdepUjR49Wlu2bNHVV1+tW2+9VSkpKQ59nQAAxzFM0zStDgEAQEnGjRunWbNmydPTs8j2J598Uk8++aQMw9C9996radOmFTzWs2dPde7cWe+++64+/PBDPfbYYzpy5Ijq1q0rSfrxxx81bNgwxcXFKTg4WA0bNtQdd9yh559/vsQMhmHoqaee0nPPPSfpXPHy9vbWTz/9xL1RAFBDcU8SAMCp9e3bt0gJkqSAgICCP0dHRxd5LDo6Wps2bZIk7dy5Ux07diwoSJJ0xRVXyG63a/fu3TIMQ3Fxcerfv3+ZGTp06FDw57p168rX11dJSUkX+5IAAE6OkgQAcGp169YtdvlbZalTp065xrm5uRX53jAM2e32qogEAHAC3JMEAKjW1qxZU+z7tm3bSpLatm2rzZs3KzMzs+Dx3377TTabTa1bt5aPj4+aNm2qpUuXOjQzAMC5cSYJAODUcnJylJCQUGSbq6ur6tevL0maN2+eunbtqiuvvFKff/65YmJiNH36dEnSrbfeqsmTJ2vs2LGaMmWKjh8/rgceeEC33XabgoODJUlTpkzRvffeq6CgIA0dOlQZGRn67bff9MADDzj2hQIAnAYlCQDg1BYuXKjQ0NAi21q3bq1du3ZJOrfy3Jdffqn77rtPoaGh+uKLLxQZGSlJ8vLy0qJFi/TQQw+pW7du8vLy0qhRo/T6668X7Gvs2LHKzs7WG2+8oUmTJql+/fq6/vrrHfcCAQBOh9XtAADVlmEY+vbbbzVixAirowAAahDuSQIAAACAQihJAAAAAFAI9yQBAKotrhgHAFQFziQBAAAAQCGUJAAAAAAohJIEAAAAAIVQkgAAAACgEEoSAAAAABRCSQIAAACAQihJAAAAAFAIJQkAAAAACvl/kbKHTIBBXrEAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Avoid parallelism error from HuggingFace during training\n",
    "tokenizer.parallelism = False\n",
    "\n",
    "# Train the model using FHE simulation\n",
    "train_custom_model(hybrid_model, train_dataloader, training_args, tokenizer, fhe=\"disable\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65d448c8",
   "metadata": {},
   "source": [
    "Note that our goal is to showcase the use of FHE for encrypted fine-tuning. The dataset consists of 68 examples and a total of 2,386 tokens, which is relatively small. Despite its limited size, which offers little support for the model's learning process, it still manages to produce interesting results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "bd666f38",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Get the fine-tuned model\n",
    "fine_tuned_model = hybrid_model.model.inference_model\n",
    "\n",
    "# Set FHE mode to disable for text generation\n",
    "hybrid_model.set_fhe_mode(\"execute\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "3e91ad0b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "who invented FHE? , the,, a, in, at, and,\n"
     ]
    }
   ],
   "source": [
    "# Inference using the fine-tuned model with LoRA weights\n",
    "# Seed for reproducibility\n",
    "torch.manual_seed(SEED)\n",
    "\n",
    "fine_tuned_model.enable_adapter_layers()\n",
    "prompt = \"who invented FHE?\"\n",
    "generate_and_print(prompt, fine_tuned_model, tokenizer, SEED)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "21e2a1d1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "What is FHE? ,, the, a, at, in,\n"
     ]
    }
   ],
   "source": [
    "# Original inference without LoRA weights\n",
    "# Seed for reproducibility\n",
    "torch.manual_seed(SEED)\n",
    "\n",
    "peft_model.disable_adapter_layers()\n",
    "\n",
    "prompt = \"What is FHE?\"\n",
    "generate_and_print(prompt, peft_model, tokenizer, SEED)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "c97425ee",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total number of weights: 124734720\n",
      "Total number of LoRA weights: 294912\n"
     ]
    }
   ],
   "source": [
    "peft_model.enable_adapter_layers()\n",
    "\n",
    "# Print weights and model size\n",
    "total_weights_size = print_weights_and_size(hybrid_model.model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "31367ff5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Save the model\n",
    "path = Path(\"deployment/gpt2_lora_finetuned\")\n",
    "path.mkdir(parents=True, exist_ok=True)\n",
    "\n",
    "if path.is_dir() and any(path.iterdir()):\n",
    "    shutil.rmtree(path)\n",
    "\n",
    "hybrid_model.save_and_clear_private_info(path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "a1dda636",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total number of weights: 39717120\n",
      "Total number of LoRA weights: 294912\n"
     ]
    }
   ],
   "source": [
    "# Print weights and size after saving\n",
    "total_weights_size_private = print_weights_and_size(hybrid_model.model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "506ad2f5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total weights removed: 68.16 %\n"
     ]
    }
   ],
   "source": [
    "# Calculate and print the percentage of weights removed\n",
    "percentage_removed = (total_weights_size - total_weights_size_private) / total_weights_size * 100\n",
    "print(f\"Total weights removed: {percentage_removed:.2f} %\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "465cb18b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Note: Around 95% of the remaining weights are from the embedding layers (wpe and wte)\n",
    "# as well as the final lm_head layer."
   ]
  }
 ],
 "metadata": {
  "execution": {
   "timeout": 10800
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
